// epoll_server.hpp(epoll 服务封装)
#pragma once
#include <string>
#include <functional>
#include <unordered_map>
#include <iostream>
#include <vector>
#include <cerrno>
#include <cassert>
#include <ctime>
#include "log.hpp"
#include "sock.hpp"
#include "connection.hpp"
#include "epoll.hpp"
#include "protocol.hpp"

namespace limou {
    /* 描述 EpollServer 服务器的类 */
    // TODO: 可以把这个服务器对象设计为单例模式
    class EpollServer {
    private:
        using func_t = std::function<void(Connection*)>;
        using callback_t = std::function<void(Connection*, std::string& request)>;
        static const int g_port; // 8080
        static const int g_num_of_revs; // 128

    private:
        // 把套接字添加到 Epoll 中进行关注
        void _AddConnection(int sock, func_t recv_cb = nullptr, func_t send_cb = nullptr, func_t except_cb = nullptr) {
            // 提前把陶杰设置套接字为非阻塞, 这是实践要求(无论时读写描述符)
            Sock::_SetNonBlock(sock);

            // 封装套接字对象, 并且设置其对应的读写回调方法
            Connection* conn = new Connection(sock, this);
            conn->SetCallBack(recv_cb, send_cb, except_cb);

            // 针对关注事件将描述符添加到 epoll 中, 然后通过 kv 树交付封装后的描述符给调用者
            // (1)添加 sock 到 epoll 中
            _poll.AddSockToEpoll(sock, EPOLLIN | EPOLLET); // 任何多路转接的服务器一般默认打开对读事件的关心(注意这里还设置成 ET 模式), 而写入事件按需进行设置, 因为写事件通常很容易就绪
            // (2)添加封装的 sock 对象指针到 conns 集合中
            _conns[sock] = conn;
        }

        // 对就绪的描述符资源分类来执行其对应的读写回调(处理一次循环)
        void _LoopOnce() {
            // 获取就绪的描述符资源个数
            int n = _poll.WaitEpoll(_revs, _num_of_revs);

            // 遍历就绪的每一个描述符资源
            for (int i = 0; i < n; i++) {
                int sock = _revs[i].data.fd; // 获取描述符
                uint32_t revents = _revs[i].events; // 获取其返回的事件

                if (revents & EPOLLERR) { // 异常事件到来(这种异常有可能在读写之前就发生, 也有可能在读写过程中发生)
                    _log.LogMessage(DEBUG, "异常事件到来, %s %d", __FILE__, __LINE__);
                    revents |= (EPOLLIN | EPOLLOUT); // 处理的时候出现异常都交给读写事件来解决, 内部的读写调用有异常的处理方法
                }

                if (revents & EPOLLHUP) { // 挂断事件到来(这种挂断有可能在读写之前就发生, 也有可能在读写过程中发生)
                    _log.LogMessage(DEBUG, "挂断事件到来, %s %d", __FILE__, __LINE__);
                    revents |= (EPOLLIN | EPOLLOUT); // 处理的时候出现挂断都交给读写事件来解决, 内部的读写调用有挂断的处理方法
                }

                if (revents & EPOLLIN) { // 读取事件到来
                    _log.LogMessage(DEBUG, "读取事件到来, %s %d", __FILE__, __LINE__);
                    if (_IsConnectionExists(sock) && _conns[sock]->_recv_cb != nullptr) {
                        /*
                        若没有使用 _IsConnectionExists() 则会出现一些特殊情况: 
                        假设后面的 _conns[sock]->_send_cb(_conns[sock]) 写入过程中才出现异常
                        内部会调用 _except_cb() 也就是 _Excepter(), 会把描述符从 unorder_map 中移除
                        而下一次 epoll() 就会检测到这个描述符出现异常, 在上面的 if 语句中给这个描述符添加 EPOLLIN | EPOLLOUT
                        事件就会导致下面的 _conns[sock]->_recv_cb(_conns[sock]); 被错误调用
                        */
                        _conns[sock]->_recv_cb(_conns[sock]); // 调用读描述符的读回调(内部会区分是监听描述符还是服务描述符的)
                    }
                    // _IsConnectionExists() 检测为真的情况就是描述符在读写之前就发生了异常/挂断
                    // _IsConnectionExists() 检测为假的情况就是描述符在读写过程中才发生异常/挂断
                }

                if (revents & EPOLLOUT) { // 写入事件到来
                    _log.LogMessage(DEBUG, "写入事件到来, %s %d", __FILE__, __LINE__);
                    if (_IsConnectionExists(sock) && _conns[sock]->_send_cb != nullptr) { // 这里使用 _IsConnectionExists() 和上面类似
                        _conns[sock]->_send_cb(_conns[sock]); // 调用写描述符的写回调
                    }
                }
            }
        }

        // 检查描述符是否已经被添加进 epoll 并且添加进 kv 树中
        bool _IsConnectionExists(int sock) {
            auto iter = _conns.find(sock);
            if (iter == _conns.end()) {
                return false;
            }
            else {
                return true;
            }
        }

        // 读写异常等回调方法的设置
        // (1)针对监听套接字读事件就绪的连接方法(是特殊的读回调)
        void _Accepter(Connection* conn) {
            while (true) { // 循环的目的是为了一次处理多个连接(不能保证只有一个), 直到读取出错, 而且就算出错也不会被阻塞
                _log.LogMessage(DEBUG, "_Accepter()...", __FILE__, __LINE__);

                // 监听套接字走到这里一定不会被阻塞
                std::string client_ip;
                uint16_t client_port;
                int accept_errno = 0;

                // 获取服务套接字
                int sock = Sock::_Accept(conn->_sock, &client_ip, &client_port, &accept_errno);

                if (sock < 0) {
                    // 获取服务套接字失败, 或者有可能是读取完所有连接
                    if (accept_errno == EAGAIN || accept_errno == EWOULDBLOCK) { // 代表不是出错, 只是没有新链接了
                        _log.LogMessage(DEBUG, "没有新的连接了... %s %d", __FILE__, __LINE__);
                        break;
                    }
                    else if (accept_errno == EINTR) { // 被信号中断, 重新链接
                        continue;
                    }
                    else {
                        _log.LogMessage(WARNING, "accept error %s %d", __FILE__, __LINE__);
                        break;
                    }
                }
                else { // sock >= 0
                    // 连接就绪了, 但是对应的读写不一定就绪, 应该继续托管给 epoll, 一开始启动的时候默认只关注描述符的读事件, 写事件关注交给服务端上层用户来决定
                    _AddConnection(
                        sock,
                        std::bind(&EpollServer::_Recver, this, std::placeholders::_1),
                        std::bind(&EpollServer::_Sender, this, std::placeholders::_1),
                        std::bind(&EpollServer::_Excepter, this, std::placeholders::_1)
                    );
                    _log.LogMessage(DEBUG, "accept success %s %d, sock->[%d]", __FILE__, __LINE__, sock);
                }
            } // end while
        }

        // (2)正常服务标识符的读回调
        void _Recver(Connection* conn) {
            _log.LogMessage(DEBUG, "Recver exists, been called _Recver(), %s %d", __FILE__, __LINE__);

            bool err = false; // 一个标志位, 防止最后调用报文分割的函数

            // 由于是 ET 模式, 因此必须一次性读取完毕, 但是读取到链接对象中的缓冲区中, 这样上层就不一定需要立刻获取数据了, 用时从缓冲区中获取即可
            while (true) { // 循环读取的原因是 recv() 一次性读取到 buffer 中的内容有限, 需要重复读取
                char buffer[1024] = { 0 };
                ssize_t n = recv(conn->_sock, buffer, sizeof(buffer) - 1, 0); // 尝试读取一次, 此处的 flag=0 已经因为我们设置了非阻塞而无效了, 不用理会了

                if (n < 0) { // 出现没有数据、挂断、错误的情况
                    if (errno == EAGAIN || errno == EWOULDBLOCK) { // 代表不是出错, 只是没有新数据可以读取了
                        break;
                    }
                    else if (errno == EINTR) { // 被信号打断了
                        continue; // 重新继续读取
                    }
                    else { // 读取出错
                        _log.LogMessage(ERROR, "recv error %s %d", __FILE__, __LINE__);
                        conn->_except_cb(conn); // 交给异常回调即可, 所有的异常都可以统一被 _Excepter() 处理
                        err = true;// 设置标志位
                        break;
                    }
                }
                else if (n == 0) { // 出现对端关闭的情况
                    _log.LogMessage(DEBUG, "client[%d] quit, server close, %s %d", conn->_sock, __FILE__, __LINE__);
                    conn->_except_cb(conn); // 跳转异常来统一处理
                    err = true;
                    break;
                }
                else // 不断读取的情况
                {
                    buffer[n] = 0;
                    conn->_inbuffer += buffer; // 由于不能保证一次读取就读取完整, 因此加入到描述符自己的缓冲区保存起来
                }
            } // end while

            _log.LogMessage(DEBUG, "conn->_inbuffer: %s, %s %d", conn->_inbuffer.c_str(), __FILE__, __LINE__);

            if (!err) { // 只要没出异常就执行下面的业务处理, 注意使用了自定义的协议
                std::vector<std::string> messages;
                SpliteMessage(conn->_inbuffer, &messages); // 注意这里 _inbuffer 会因为分割取走数据
                for (auto& msg : messages) { // 如果缺失报文那这里列表 messages 就为空
                    // 处理每一个完整的报文, 注意这里不要和服务端业务强耦合, 因此这里直接根据用户需求, 把完整报文按照服务端上层用户的要求进行回调即可
                    _cb(conn, msg); // 执行服务端上层用户的回调方法, 内部是对客户端请求的处理(我设置为了把从客户端读到的数据处理过后放入对应服务描述符的写入缓冲区中, 同时打开 epoll 对写描述符的关注...)
                    // TODO: 实际上也可以把许多的任务做任务队列, 进行多线程处理
                }
            }
        }

        // (3)正常服务标识符的写回调
        void _Sender(Connection* conn) {
            _log.LogMessage(DEBUG, "Sender exists, been called _Sender(), %s %d", __FILE__, __LINE__);

            while (true) { // 循环写入的原因是有可能对方的缓冲区可能无法一次承受所有的数据, 而由于是非阻塞描述符, 就会返回已经发送的数据个数, 可能需要等待对方缓冲区有空间再次写入
                ssize_t n = send(conn->_sock, conn->_outbuffer.c_str(), conn->_outbuffer.size(), 0);
                if (n > 0) {
                    conn->_outbuffer.erase(0, n); // 已经发送的数据就移除

                    if (conn->_outbuffer.empty()) { // 如果读取完了就退出
                        break;
                    }
                }
                else {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) { // 发送缓冲区可能还有数据, 但只是对面缓冲区满了, 过会再发
                        break;
                    }
                    else if (errno == EINTR) { // 信号中断的情况
                        continue;
                    }
                    else { // 出现异常错误 
                        _log.LogMessage(ERROR, "Sender error, %s %d", __FILE__, __LINE__);
                        conn->_except_cb(conn); // 异常就另外处理就绪
                        break;
                    }
                }
            } // end while

            // 发不发完是不确定的, 但是可以保证没有出错的话, 要么发完, 要么条件不满足, 下次发就行
            if (conn->_outbuffer.empty()) { // 如果发送完了
                EnableReadWrite(conn, true, false); // 把写关注关闭
            }
            else { // 如果还没发送完, 需要等待对端有空间被写入, 就需要继续关注写事件
                EnableReadWrite(conn, true, true); // 把写关注打开
            }
        }

        // (4)正常服务标识符的异常回调
        void _Excepter(Connection* conn) {
            _log.LogMessage(DEBUG, "Excepter exists, been called _Excepter(), %s %d", __FILE__, __LINE__);
            if (!_IsConnectionExists(conn->_sock)) { // 不存在就不处理了
                return;
            }
            
            // 1.从 epoll 中移除
            bool res = _poll.DelFromEpoll(conn->_sock);
            assert(res);

            // 2.从 unorder_map 中移除
            _conns.erase(conn->_sock);

            // 3.关闭文件描述符
            close(conn->_sock);

            // 4.销毁对套接字的封装
            delete conn;

            _log.LogMessage(DEBUG, "Recovery completed!!! %s %d", __FILE__, __LINE__);
        }

        
    public:
        // 创建监听套接字, 设置好服务器的 ip:port, 初始化 epoll 模型, 准备好就绪缓冲区
        EpollServer(const int& port = g_port)
            : _listensock(Sock::_Socket())
            , _port(port)
            , _poll(Epoll(0))
            , _num_of_revs(g_num_of_revs) {
            
            // 准备好监听套接字
            Sock::_Bind(_listensock, _port);
            Sock::_Listen(_listensock);

            // 封装监听描述符, 规定其读写异步事件(尤其是特殊的读事件), 并且添加到 epoll 中
            // 注意这里的绑定会导致函数类型从 void ()(const EpollServer*, Connection*) 变为 void ()(Connection*) 
            _AddConnection(_listensock, std::bind(&EpollServer::_Accepter, this, std::placeholders::_1), nullptr, nullptr);

            // 准备就绪缓冲区, 未来会存储每一次 epoll 中就绪的描述符
            _revs = new struct epoll_event[_num_of_revs]; // TODO: 可进行扩容
        }

        // 释放监听套接字资源和 epoll 模型资源
        ~EpollServer() {
            if (_listensock >= 0) {
                close(_listensock);
            }

            if (_revs) {
                delete[] _revs;
            }

            // 还会自动执行 Epoll 中的析构函数销毁 _poll 内部的 epoll 模型
        }

        // 根据特定的事件进行事件就绪查询, 而 cb 是对服务端上层用户对客户端报文的处理回调方法
        void Dispather(callback_t cb) {
            _cb = cb;
            while (true) {
                _LoopOnce();
            }
        }

        // 读写使能接口, 可以设置一个描述符的读写事件, 同时加上 ET 模式, 主要方便服务端上层用户请求写描述符被 epoll 进行关注
        void EnableReadWrite(Connection* conn, bool readable, bool writeable) {
            uint32_t events = (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0);
            bool res = _poll.CtrlEpoll(conn->_sock, events);
            assert(res);
        }

    private:
        int _listensock;
        int _port;

        Epoll _poll; // 封装过的 Epoll 对象
        std::unordered_map<int, Connection*> _conns; // 多个封装的连接就可以被一起在这里被管理(但是它们不一定是就绪的)
        struct epoll_event* _revs; // 存储就绪描述符资源
        int _num_of_revs; // 一次存储就绪描述符资源的大小

        callback_t _cb; // 上层用户需要对客户端报文的处理回调方法

        Log _log;
    };

    const int EpollServer::g_port = 8080; // 定义静态成员变量 g_port
    const int EpollServer::g_num_of_revs = 128; // 定义静态成员变量 g_num_of_revs
}